/*
 * Copyright (C) 2009 Christoph Caks
 *
 * This file is part of GLWAK.
 *
 * GLWAK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * GLWAK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with GLWAK.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * WindowsWindow.cpp
 *
 *  Created on: 31.07.2009
 *      Author: Christoph Caks
 */

#include "windows/WindowsWindow.h"

#ifndef LOG_DEBUG
#define LOG_DEBUG std::cout
#define LOG_DEBUG1 std::cout
#define LOG_DEBUG2 std::cout
#define LOG_DEBUG3 std::cout
#define LOG_DEBUG4 std::cout
#define LOG_ERROR std::cerr
#define LOG_INFO std::cout
#endif

#define GLERR { \
	int error = glGetError(); \
	if ( !error == GL_NO_ERROR ) { \
		LOG_ERROR << "OpenGL Error occurred. Error Code = " << error; \
	} \
	else { \
		LOG_DEBUG4 << " -> No OpenGL error occurred"; \
	} \
}

namespace glwak {

namespace priv {

WindowsWindow::WindowsWindow() : Window() {
}

WindowsWindow::~WindowsWindow() {
}

size_t WindowsWindow::getWindowHandle() {
	return (size_t) window;
}

void WindowsWindow::initializeImpl() {
	LOG_DEBUG4 << "[WindowsWindow] initializeImpl() - begin";
	CHAR szClassName[] = "GLWAK Window Class";

    WNDCLASSEX wndclassex;
    wndclassex.cbSize			= sizeof( WNDCLASSEX );
    wndclassex.style			= CS_OWNDC;
    wndclassex.lpfnWndProc		= &WindowsWindow::WindowProc;
    wndclassex.cbClsExtra		= 0;
    wndclassex.cbWndExtra		= 0;
    wndclassex.hInstance		= GetModuleHandle( NULL );
    wndclassex.hIcon			= LoadIcon(NULL, IDI_APPLICATION);
    wndclassex.hCursor			= LoadCursor(NULL, IDC_ARROW);
    wndclassex.hbrBackground	= 0;
    wndclassex.lpszMenuName		= 0;
    wndclassex.lpszClassName	= szClassName;
    wndclassex.hIconSm			= 0;

    ATOM wndClassAtom = RegisterClassEx( &wndclassex );

    if ( !wndClassAtom ) {
    	const std::string error = glwak::util::WindowsHelper::getLastError();
		LOG_ERROR << "could not register window class: " << error;
		throw error.c_str();
    }

    LOG_DEBUG3 << "[WindowsWindow] initializeImpl() - creating window";
	window = CreateWindowEx(
		WS_EX_WINDOWEDGE,
	    szClassName,
	    configuration.getCaption().c_str(),
	    WS_BORDER | WS_CAPTION,
	    CW_USEDEFAULT,
	    CW_USEDEFAULT,
	    configuration.getWidth(),
	    configuration.getHeight(),
	    NULL,
	    NULL,
	    GetModuleHandle(NULL),
	    NULL
	);
	if ( !window ) {
	   	const std::string error = glwak::util::WindowsHelper::getLastError();
		LOG_ERROR << "could not create window: " << error;
		throw error.c_str();
	}
	LOG_DEBUG3 << "[WindowsWindow] initializeImpl() - success window=" << window;

	if ( configuration.isFullscreen() ) {
		this->fullscreen();
	}

	LOG_DEBUG4 << "[WindowsWindow] initializeImpl() - end";
}

void WindowsWindow::fullscreen() {

	DEVMODE devMode;
	devMode.dmSize = sizeof( devMode );
	devMode.dmPelsWidth = configuration.getWidth();
	devMode.dmPelsHeight = configuration.getHeight();
	devMode.dmBitsPerPel = 32;
	//devMode.dmDisplayFrequency = 60;
	devMode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT | DM_BITSPERPEL;

	switch ( ChangeDisplaySettings( &devMode, CDS_FULLSCREEN ) ) {
	case DISP_CHANGE_SUCCESSFUL:
		break;
	case DISP_CHANGE_BADDUALVIEW:
		LOG_ERROR << "[WindowsWindow] fullscreen() - DISP_CHANGE_BADDUALVIEW";
		throw "could not change display settings";
		break;
	case DISP_CHANGE_BADFLAGS:
		LOG_ERROR << "[WindowsWindow] fullscreen() - DISP_CHANGE_BADFLAGS";
		throw "could not change display settings";
		break;
	case DISP_CHANGE_BADMODE:
		LOG_ERROR << "[WindowsWindow] fullscreen() - DISP_CHANGE_BADMODE";
		throw "could not change display settings";
		break;
	case DISP_CHANGE_BADPARAM:
		LOG_ERROR << "[WindowsWindow] fullscreen() - DISP_CHANGE_BADPARAM";
		throw "could not change display settings";
		break;
	case DISP_CHANGE_FAILED:
		LOG_ERROR << "[WindowsWindow] fullscreen() - DISP_CHANGE_FAILED";
		throw "could not change display settings";
		break;
	case DISP_CHANGE_NOTUPDATED:
		LOG_ERROR << "[WindowsWindow] fullscreen() - DISP_CHANGE_NOTUPDATED";
		throw "could not change display settings";
		break;
	case DISP_CHANGE_RESTART:
		LOG_ERROR << "[WindowsWindow] fullscreen() - DISP_CHANGE_RESTART";
		throw "could not change display settings";
		break;
	}

	SetWindowLong( window, GWL_STYLE,   WS_POPUP );
	SetWindowLong( window, GWL_EXSTYLE, WS_EX_APPWINDOW );

	SetWindowPos( window, HWND_TOP, 0, 0, configuration.getWidth(), configuration.getHeight(), SWP_FRAMECHANGED );
	ShowWindow( window, SW_SHOW );

    // SetPixelFormat can fail (really ?) if window style doesn't contain these flags
    long style = GetWindowLong( window, GWL_STYLE);
    SetWindowLong( window, GWL_STYLE, style | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
}


void WindowsWindow::reinitializeImpl() {
	LOG_DEBUG4 << "[WindowsWindow] reinitializeImpl() - begin";

	if ( configuration.isFullscreen() ) {
		this->fullscreen();
	}
	else {
		ChangeDisplaySettings( NULL, 0 );
		SetWindowLong( window, GWL_STYLE,   WS_BORDER | WS_CAPTION );
		SetWindowLong( window, GWL_EXSTYLE, WS_EX_WINDOWEDGE );
		SetWindowPos( window, HWND_TOP, CW_USEDEFAULT, CW_USEDEFAULT, configuration.getWidth(), configuration.getHeight(), SWP_NOMOVE | SWP_FRAMECHANGED | SWP_SHOWWINDOW );
		ShowWindow( window, SW_SHOW );
	}

	LOG_DEBUG4 << "[WindowsWindow] reinitializeImpl() - end";
}

void WindowsWindow::show() {
	LOG_DEBUG4 << "[WindowsWindow] show() - begin";
	ShowWindow( window, SW_SHOWNORMAL );

	BOOL result = UpdateWindow( window );
	if ( ! result ) {
	   	const std::string error = glwak::util::WindowsHelper::getLastError();
		LOG_ERROR << "could not update window: " << error;
	}
	LOG_DEBUG4 << "[WindowsWindow] show() - end";
}

void WindowsWindow::hide() {
	LOG_DEBUG4 << "[WindowsWindow] hide() - begin";
	ShowWindow( window, SW_HIDE );
	LOG_DEBUG4 << "[WindowsWindow] hide() - end";
}

void WindowsWindow::dispose() {
	LOG_DEBUG4 << "[WindowsWindow] dispose() - begin";
	if ( ! DestroyWindow( window ) ) {
	   	const std::string error = glwak::util::WindowsHelper::getLastError();
		LOG_ERROR << "could not dispose window: " << error;
		throw error.c_str();
	}
	LOG_DEBUG4 << "[WindowsWindow] dispose() - end";
}

HWND WindowsWindow::getWindow() {
	LOG_DEBUG4 << "[WindowsWindow] getWindow() - returns: " << this->window;
	return this->window;
}

void WindowsWindow::startMessageLoop() {
	LOG_DEBUG4 << "[WindowsWindow] startMessageLoop() - begin";
	this->initialize();
	MSG msg;
	BOOL bRet;
	while ( ( bRet = GetMessage( &msg, NULL, 0, 0 ) ) != 0 ) {
		//std::cout << "handling message" << std::endl;
		if ( bRet == -1 ) {
		   	const std::string error = glwak::util::WindowsHelper::getLastError();
			LOG_ERROR << "could not dispose window: " << error;
			throw error.c_str();
		}
		else {
			TranslateMessage( &msg );
			DispatchMessage( &msg );
		}
	}
	LOG_DEBUG4 << "[WindowsWindow] startMessageLoop() - end";
}

void WindowsWindow::startMessageThread() {
	LOG_DEBUG4 << "[WindowsWindow] startMessageThread() - begin";
	OpenThreads::Thread::SetConcurrency(3);
	OpenThreads::Thread::Init();

	if ( 0 != this->start() ) {
		LOG_ERROR << "[WindowsWindow] startMessageThread() error starting thread!";
	}
	LOG_DEBUG4 << "[WindowsWindow] startMessageThread() - end";
}

void WindowsWindow::run() {
	LOG_DEBUG4 << "[WindowsWindow] run() - begin";
	this->startMessageLoop();
	LOG_DEBUG4 << "[WindowsWindow] run() - end";
}

// TODO: for now copied from msdn - need to modify
LRESULT CALLBACK WindowsWindow::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {
    LONG    lRet = 1;
    PAINTSTRUCT    ps;
    RECT rect;

    switch (uMsg) {

    case WM_CREATE:
    	LOG_DEBUG << "WM_CREATE MESSAGE";
    	// This should not be here - needs to be in WindowsContext
//        HDC deviceContext = GetDC( hWnd );
//        if ( !bSetupPixelFormat( deviceContext ) )
//            PostQuitMessage (0);

//        HGLRC glRenderingContext = wglCreateContext( deviceContext );
//        wglMakeCurrent(ghDC, ghRC);
//        GetClientRect(hWnd, &rect);
//        initializeGL(rect.right, rect.bottom);
        break;

    case WM_PAINT:
    	LOG_DEBUG << "WM_PAINT MESSAGE";
        BeginPaint( hwnd, &ps );
        EndPaint( hwnd, &ps );
        break;

    case WM_SIZE:
    	LOG_DEBUG << "WM_SIZE MESSAGE";
//        GetClientRect(hWnd, &rect);
//        resize(rect.right, rect.bottom);
        break;

    case WM_CLOSE:
    	LOG_DEBUG << "WM_CLOSE MESSAGE";
//        if (ghRC)
//            wglDeleteContext(ghRC);
//        if (ghDC)
//            ReleaseDC(hWnd, ghDC);
//        ghRC = 0;
//        ghDC = 0;
//
//        DestroyWindow (hWnd);
        break;

    case WM_DESTROY:
    	LOG_DEBUG << "WM_DESTROY MESSAGE";
//    	this->dispose();
//        if (ghRC)
//            wglDeleteContext(ghRC);
//        if (ghDC)
//            ReleaseDC(hWnd, ghDC);
//
//        PostQuitMessage (0);
        break;

    case WM_KEYDOWN:
    	LOG_DEBUG << "WM_KEYDOWN MESSAGE";
//        switch (wParam) {
//        case VK_LEFT:
//            longinc += 0.5F;
//            break;
//        case VK_RIGHT:
//            longinc -= 0.5F;
//            break;
//        case VK_UP:
//            latinc += 0.5F;
//            break;
//        case VK_DOWN:
//            latinc -= 0.5F;
//            break;
//        }
    	break;
    default:
        lRet = DefWindowProc( hwnd, uMsg, wParam, lParam );
        break;
    }

    return lRet;

}

} // namespace priv

} // namespace glwak
